package com.sz.common.core.system.service.impl;

import com.sz.common.base.constants.UserActions;
import com.sz.common.base.dao.ParamData;
import com.sz.common.base.exception.Exceptions;
import com.sz.common.base.service.AbstractService;
import com.sz.common.base.utils.EntityUtils;
import com.sz.common.core.service.PrincipalLogUtils;
import com.sz.common.core.constants.SysErrorCodes;
import com.sz.common.core.constants.ModuleNames;
import com.sz.common.core.system.dto.MenuPermissionDto;
import com.sz.common.core.system.entity.Authorization;
import com.sz.common.core.system.entity.Menu;
import com.sz.common.core.system.entity.MenuPermission;
import com.sz.common.core.system.service.AuthorizationService;
import com.sz.common.core.system.service.MenuPermissionService;
import com.sz.common.core.system.service.MenuService;
import org.apache.commons.collections.map.HashedMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;

/**
 * Function: 授权serviceImpl <br>
 * Author: hpeng.wang <br>
 * Date: 2016-10-26 13:41
 */
@Service("authorizationService")
public class AuthorizationServiceImpl extends AbstractService implements AuthorizationService {
    @Autowired
    private MenuService menuService;
    @Autowired
    private MenuPermissionService menuPermissionService;

    @Override
    protected String getMapperNamespace() {
        return "AuthorizationMapper";
    }

    @Override
    public void deleteAuthByResId(List<Integer> resIds) {
        if (!CollectionUtils.isEmpty(resIds)) {//当资源id不为空时，执行操作
            dao.delete(getSqlName("deleteAuthByResId"), resIds);
            //保存操作日志
            String log = String.format("根据资源ids" + String.valueOf(resIds) + "%s", "删除相应的授权信息集合");
            PrincipalLogUtils.addOperationLog(ModuleNames.SYS, ModuleNames.SYS_MENU, UserActions.DELETE, log, null);
        }
    }

    @Override
    public List<Authorization> findAuthListByRoleIds(List<Integer> roleIds, Integer resType) {
        List<Authorization> authorizationList = new ArrayList<>();
        if (roleIds != null && roleIds.size() > 0) {
            ParamData paramData = new ParamData();
            paramData.put("roleId", roleIds);
            paramData.put("resType", resType);
            //调用方法查询数据
            authorizationList = dao.findForList(getSqlName("findAuthListByRoleIds"), paramData, Authorization.class);
        }

        return authorizationList;
    }

    @Override
    public List<Integer> findPermissionIdsByRoleIds(List<Integer> roleIds, Integer resType) {
        List<Integer> permissionIds = new ArrayList<>();
        if (roleIds == null || roleIds.size() == 0) {
            return permissionIds;
        }
        ParamData paramData = new ParamData();
        paramData.put("roleId", roleIds);
        paramData.put("resType", resType);
        //调用方法查询数据
        permissionIds = dao.findForList(getSqlName("findPermissionIdsByRoleIds"), paramData, Integer.class);
        return permissionIds;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public int save(Authorization entry) {
        int count = 0;
        if (entry != null) {
            //保存菜单权限
            dao.save(getSqlName("insertSelective"), entry);
            count = entry.getId();
            //添加日志信息
            Map<String, Object> dataBackup = new HashedMap();
            dataBackup.put("ADD_SYS_MENU", entry);
            PrincipalLogUtils.addOperationLog(ModuleNames.SYS, ModuleNames.SYS_MENU, UserActions.ADD, "添加菜单权限", dataBackup);
        }
        return count;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void saveAuth(List<Integer> menuIds, Integer roleId) {
        if (roleId == 1) {
            throw Exceptions.bizException(SysErrorCodes.ERROR_AUTH_MENU_ROOT);
        }
        Map<String, Object> dataBackup = new HashMap<>();
        //保存菜单
        Map<String, Object> menuBackup = saveMenu(menuIds, roleId);
        dataBackup.putAll(menuBackup);
        //保存权限
        Map<String, Object> permissionBackup = savePerm(menuIds, roleId);
        permissionBackup.forEach((key,val) -> {
            if (dataBackup.containsKey(key)) {
                List value = (List) dataBackup.get(key);
                value.addAll((Collection) val);
                dataBackup.put(key, value);
            }
        });

        if (dataBackup.size() > 0) { //向系统添加日志
            PrincipalLogUtils.addOperationLog(ModuleNames.SYS, ModuleNames.SYS_MENU,
                    UserActions.ADD, "添加菜单权限", dataBackup);
        }

    }

    /**
     * 删除角色分配的菜单（根据角色id）
     *
     * @param roleId 对应的角色id
     */
    @Override
    public void deleteAuthByRoleId(int roleId) {
        if (roleId > 0) {
            dao.delete(getSqlName("deleteAuthByRoleId"), roleId);
            //保存操作日志
            Map<String, Object> dataBackup = new HashMap<>();
            dataBackup.put("DELETE_MENU", roleId);
            PrincipalLogUtils.addOperationLog(ModuleNames.SYS, ModuleNames.SYS_MENU, UserActions.DELETE, "删除菜单", dataBackup);
        }
    }

    private Map<String, Object> saveMenu(List<Integer> menuIds, Integer roleId) {
        menuIds = new ArrayList<>(menuIds);
        //1、计算哪些菜单应该被保存
        //1.1、取出非叶子节点(也就是父)的菜单
        List<Menu> parentMenus = menuService.findByParentIds(menuIds);
        //1.2、从menuIds中删除非叶子节点的数据，得到的menuIds是要保存到数据库中的叶子节点
        if (parentMenus != null && parentMenus.size() > 0) {
            menuIds.removeAll(new HashSet<>(EntityUtils.getIntPropListFromBeans(parentMenus,
                    "parentId")));
        }

        //根据menuIds得到当前应保存的叶子节点对应的菜单对象信息，并取出其对应的menuId集合currMenuIdList
        List<Menu> currMenuList = menuService.findByIds(menuIds, null);
        List<Integer> currMenuIdList = new ArrayList<>();
        currMenuList.forEach(menu -> currMenuIdList.add(menu.getId()));


        //2、根据roleIds从授权表（sys_authorization）查出目前在授权表（sys_authorization）里已经保存的菜单信息，
        // 从对应的集合中取出对应的菜单id(originalResIds)集合
        List<Authorization> originalAuthList = this.findAuthListByRoleIds(Arrays.asList(roleId), 1); //此处不会为NULL,
        // 查询出菜单与授权的所有数据。
        List<Integer> originalResIds = new ArrayList<>();
        originalAuthList.forEach(authorization -> originalResIds.add(authorization.getResId()));


        //3、对将要保存的菜单id（currMenuList）与已经存在的菜单id（originalResIds），进行比对获得其共同存在的数据，做移除，
        // （因为共同存在的数据已经存在不需要再保存），然后对原始集合中的数据做删除操作，对将要保存中的数据做保存操作。
        List<Integer> togetherMenuId = new ArrayList<>();
        originalResIds.stream().filter(integer -> currMenuIdList.contains(integer)).forEach
                (togetherMenuId::add);
        //3.1对将要保存的菜单id（currMenuList）与已经存在的菜单id（originalResIds）中去除相同的数据
        originalResIds.removeAll(togetherMenuId);
        currMenuIdList.removeAll(togetherMenuId);

        //删除originalResIds留下来的授权信息，（因为这些信息在currMenuIdList中不被保存）
        this.deleteAuthByRoleIdAndResId(roleId, originalResIds, 1);

        //4.1、构建日志需要的信息
        //4.1.1从原始的授权中删除的菜单信息
        List<Object> logData = new ArrayList<>();
        for (Authorization au : originalAuthList) {
            if (originalResIds.contains(au.getResId())) {
                logData.add(au);
            }
        }
        Map<String, Object> dataBackup = new HashMap<>();
        if (logData != null && logData.size() > 0) {
            dataBackup.put(UserActions.DELETE, logData);
        }

        //在向某个角色授权某个菜单的时时候，先把菜单加入到sys_authorization表

        //5、添加currIdList中的数据，组装要保存的菜单权限信息
        List<Authorization> saveList = new ArrayList<>();
        for (Integer id : currMenuIdList) {
            Authorization auth = new Authorization();
            auth.setResId(id);
            auth.setResType((byte) 1);//菜单
            auth.setRoleId(roleId);
            auth.setPermissionId(1);//如果授权表中保存的是菜单，这个字段目前不用，统一写成1,这个1来自于sys_permission表的permissionId
            saveList.add(auth);
        }
        //向数据库里保存菜单并添加日志信息到集合中
        if (saveList != null && saveList.size() > 0) {
            this.batchSaveAuth(saveList);
            dataBackup.put(UserActions.ADD, saveList);
        }
        //返回日志信息
        return dataBackup;
    }

    private Map<String, Object> savePerm(List<Integer> menuIds, Integer roleId) {

        //1、得到menuIds在表sys_menu_permission的所有权限，并进行拼装成menuId_permission的样子
        List<MenuPermission> currMenuPermissions = menuPermissionService.findMenuPerListByMenuIds(menuIds);
        Map<String, Integer> currPliceData = new HashMap<>();
        currMenuPermissions.forEach(menuPermission ->
                currPliceData.put(menuPermission.getMenuId() + "_" + menuPermission.getPermissionId(),
                        menuPermission.getId()));


        //2、根据roleIds从表sys_authorization中查询出目前在授权表（sys_authorization）里已经保存的授权信息（type＝2），并进行拼装成menuId_permission的样子
        List<Authorization> originalMenuPermissions = this.findAuthListByRoleIds(Arrays.asList(roleId), 2); //此处不会为NULL,
        Map<String, Integer> originalPliceData = new HashMap<>();
        originalMenuPermissions.forEach(authorization ->
                originalPliceData.put(authorization.getResId() + "_" + authorization.getPermissionId(), authorization
                        .getId()));


        //3、对将要保存的菜单id（currPliceData）与已经存在的菜单id（originalPliceData），进行比对获得其共同存在的数据，做移除，
        // （因为共同存在的数据已经存在不需要再保存），然后对原始集合中的数据做删除操作，对将要保存中的数据做保存操作。
        List<String> togetherMenuId = new ArrayList<>();

        originalPliceData.keySet().stream().filter(string -> currPliceData.keySet().contains(string)).forEach
                (togetherMenuId::add);
        //3.1对将要保存的菜单id（currMenuList）与已经存在的菜单id（originalResIds）中去除相同的数据

        for (String key : togetherMenuId) {
            originalPliceData.remove(key);
            currPliceData.remove(key);
        }

        //从表sys_authorization中删除originalPliceData中留下来的授权信息，（因为这些信息在currMenuIdList中不被保存）

        List<Integer> deleteData = new ArrayList<>();
        for (Integer authId : originalPliceData.values()) {
            deleteData.add(authId);
        }
        this.batchDeleteAuth(deleteData);

        //4.1、构建日志需要的信息
        //4.1.1从原始的授权中删除的菜单信息
        List<Object> logData = new ArrayList<>();
        for (Authorization auth : originalMenuPermissions) {
            if (originalPliceData.values().contains(auth.getId())) {
                logData.add(auth);
            }
        }

        Map<String, Object> dataBackup = new HashMap<>();
        if (logData != null && logData.size() > 0) {
            dataBackup.put(UserActions.DELETE, logData);
        }
        List<Authorization> saveList = new ArrayList<>();
        for (MenuPermission menuPerm : currMenuPermissions) {
            if (currPliceData.values().contains(menuPerm.getId())) {
                Authorization auth = new Authorization();
                auth.setResType((byte) 2);//权限
                auth.setRoleId(roleId);
                auth.setPermissionId(menuPerm.getPermissionId());
                auth.setResId(menuPerm.getMenuId());//菜单id
                saveList.add(auth);
            }
        }

        //对角色菜单权限与角色资源权限进行保存
        if (saveList != null && saveList.size() > 0) {
            this.batchSaveAuth(saveList);
            dataBackup.put(UserActions.ADD, saveList);
        }

        //返回日志信息
        return dataBackup;
    }

    /**
     * 批量添加授权
     *
     * @param list 要添加的授权集合
     */
    private void batchSaveAuth(List<Authorization> list) {
        dao.batchInsert(getSqlName("batchSaveAuth"), list);
    }

    /**
     * 批量删除授权
     *
     * @param ids 要删除的授权的id集合
     */
    private void batchDeleteAuth(List<Integer> ids) {
        if (ids != null && ids.size() > 0) {
            dao.delete(getSqlName("batchDeleteAuth"), ids);
        }
    }

    private void deleteAuthByRoleIdAndResId(Integer roleId, List<Integer> resIds, Integer type) {
        if (roleId != null && resIds != null && resIds.size() > 0) {
            ParamData pd = new ParamData();
            pd.put("roleId", roleId);
            pd.put("list", resIds);
            pd.put("resType", type);
            dao.delete(getSqlName("deleteAuthByRoleIdAndResId"), pd);
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void saveMenuAndPermissionAuth(List<MenuPermissionDto> list, Integer roleId) {
        if (list != null) {
            //获取数据库存储的该角色的所有权限
            ParamData paramData = new ParamData();
            paramData.put("roleId", Arrays.asList(roleId));
            paramData.put("resType", 2);
            List<Authorization> originalPermissions = dao.findForList(getSqlName("findAuthListByRoleIds"), paramData, Authorization.class);
            boolean needRemoveOriginalPermission = list.size() <= 0;
            List<Integer> menusIds = new ArrayList<>();
            for (MenuPermissionDto menuPermissionDto : list) {
                //删除错误的menu 与permission不匹配的数据
                List<MenuPermission> currMenuPermissions = menuPermissionService.findMenuPerListByMenuId(menuPermissionDto.getMenuId());
                List<Integer> currPermissionIdList = menuPermissionDto.getPermissionIds();
                if (currPermissionIdList != null && currPermissionIdList.size() > 0) {
                    Iterator<Integer> it = currPermissionIdList.iterator();
                    while (it.hasNext()) {
                        int permissionId = it.next();
                        if (currMenuPermissions.stream().noneMatch(currMenuPermission -> currMenuPermission.getPermissionId().equals(permissionId))) {
                            it.remove();
                        }
                    }

                    menusIds.add(menuPermissionDto.getMenuId());
                    List<Authorization> currentMenuPermission = new ArrayList<>();
                    if (originalPermissions != null && originalPermissions.size() > 0) {
                        originalPermissions.stream()
                                .filter(authorization -> authorization.getResId().equals(menuPermissionDto.getMenuId()))
                                .forEach(currentMenuPermission::add);
                        // 把当前菜单的权限从总权限里面移除掉
                        originalPermissions.removeAll(currentMenuPermission);
                    }
                    if (currentMenuPermission.size() > 0) {
                        needRemoveOriginalPermission = true;
                        //数据库不需要删除的权限
                        List<Authorization> togetherPermission = new ArrayList<>();
                        currentMenuPermission.stream().filter(au -> currPermissionIdList.contains(au.getPermissionId())).forEach(togetherPermission::add);
                        //获取该菜单上需要删除的权限
                        currentMenuPermission.removeAll(togetherPermission);

                        //筛选出来需要保存的权限
                        for (Authorization authorization2 : togetherPermission) {
                            if (currPermissionIdList.contains(authorization2.getPermissionId())) {
                                currPermissionIdList.remove(authorization2.getPermissionId());
                            }
                        }
                        List<Integer> needDeletePermissionIds = new ArrayList<>();
                        for (Authorization currMenuPermission : currentMenuPermission) {
                            needDeletePermissionIds.add(currMenuPermission.getPermissionId());
                        }
                        //删除原先存在但是需要删除的权限
                        this.deleteAuthByRoleIdPermissionIdAndMenuId(roleId, needDeletePermissionIds, 2, menuPermissionDto.getMenuId());
                    }
                    //保存需要保存的数据
                    List<Authorization> saveList = new ArrayList<>();
                    for (Integer permissionId : currPermissionIdList) {
                        Authorization authorzation = new Authorization();
                        authorzation.setPermissionId(permissionId);
                        authorzation.setResType((byte) 2);
                        authorzation.setRoleId(roleId);
                        authorzation.setResId(menuPermissionDto.getMenuId());
                        saveList.add(authorzation);
                    }

                    if (saveList != null && saveList.size() > 0) {
                        this.batchSaveAuth(saveList);
                    }
                }
            }
            //删除掉不在勾选菜单里面 但是在数据库中角色具有的权限
            if (needRemoveOriginalPermission) {
                for (Authorization needDeleteOriginalPermission : originalPermissions) {
                    this.deleteAuthByRoleIdPermissionIdAndMenuId(roleId, Arrays.asList(needDeleteOriginalPermission.getPermissionId()), 2, needDeleteOriginalPermission.getResId());
                }
            }

            saveMenu(menusIds, roleId);
        }
    }

    private void deleteAuthByRoleIdPermissionIdAndMenuId(Integer roleId, List<Integer> originalPermissionIds, int type, int menuId) {
        if (roleId != null && originalPermissionIds != null && originalPermissionIds.size() > 0) {
            ParamData pd = new ParamData();
            pd.put("roleId", roleId);
            pd.put("list", originalPermissionIds);
            pd.put("resType", type);
            pd.put("resId", menuId);
            dao.delete(getSqlName("deleteAuthByRoleIdPermissionIdAndResId"), pd);
        }
    }
}
